﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;

namespace WikiDoc.Formatter
{
    /// <summary>
    /// 用来对调用者进行测试的 IFormatter 接口的实现。
    /// 
    /// </summary>
    /// <remarks>
    /// FormatterTester类并不输出任何实质的内容，而是对调用 IFormatter 接口的各个方法进行校验。
    /// 可以用本类来测试一个向 IFormatter 输出信息的对象是否工作正常。
    /// 
    /// FormatterTester类中使用 WriteState 来保存当前写入器的状态（正在完成标签还是开始写入内容等）。
    /// 一些方法只能在特定的状态下被调用才是正常的，例如，还没开始写入标签，就不应该
    /// 调用写入属性的方法。
    /// 
    /// FormatterTester类还使用了一个堆栈来保存当前没有关闭的标签，这被用来验证标签关闭的顺序。
    /// </remarks>
    public class FormatterTester : IFormatter
    {
        /// <summary>
        /// Initializes a new instance of the FormatterTester class.
        /// </summary>
        public FormatterTester()
        {
            _writeState = WriteState.Start;
        }

        public void Reset()
        {
            _writeState = WriteState.Start;
            _stack.Clear();
        }

        /// <summary>
        /// Gets or sets the writer.
        /// </summary>
        public TextWriter Writer { get; set; }

        private WriteState _writeState;
        /// <summary>
        /// Gets the state of the write.
        /// </summary>
        /// <value>The state of the write.</value>
        public WriteState WriteState
        {
            get
            {
                return _writeState;
            }
        }

        private readonly Stack<string> _stack = new Stack<string>(7);
        /// <summary>
        /// Gets the stack.
        /// </summary>
        /// <value>The stack.</value>
        public Stack<string> Stack
        {
            get
            {
                return _stack;
            }
        }

        /// <summary>
        /// 有效的名称必须以字母开头，后续字母或数字，只能是小写
        /// </summary>
        static readonly System.Text.RegularExpressions.Regex _validNameReg = new System.Text.RegularExpressions.Regex(
            @"\A[a-z_][0-9a-z_-]*\z", 
            System.Text.RegularExpressions.RegexOptions.Compiled 
            | System.Text.RegularExpressions.RegexOptions.CultureInvariant
            | System.Text.RegularExpressions.RegexOptions.Singleline
            );

        /// <summary>
        /// Validates the name.
        /// </summary>
        /// <param name="name">The name string.</param>
        /// <param name="paramterName">Name of the paramter.</param>
        public static void ValidateName(string name, string paramterName)
        {
            if (name == null) throw new ArgumentNullException("name");
            if (paramterName == null) throw new ArgumentNullException("paramterName");

            if (!_validNameReg.IsMatch(name)) throw new ArgumentException(
                string.Format("错误的{0}名称 {1}", paramterName, name), paramterName);
        }

        /// <summary>
        /// Begins the tag.
        /// </summary>
        /// <param name="tag">The tag.</param>
        /// <remarks>
        /// 
        /// </remarks>
        public void BeginTag(string tag)
        {
            ValidateName(tag, "tag");

            if (_writeState != WriteState.Start &&  // 开始前
                _writeState != WriteState.Content && // 在上一段内容之后
                _writeState != WriteState.Attribute // 在上一个标签之后
                )
            {
                throw new InvalidOperationException(
                    string.Format("在错误的状态 [{0}] 下调用了 BeginTag", _writeState));
            }

            _stack.Push(tag);

            _writeState = WriteState.Attribute;
        }


        /// <summary>
        /// Adds the attribute.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="value">The value.</param>
        public void AddAttribute(string name, string value)
        {
            ValidateName(name, "name");
            if (value == null) throw new ArgumentNullException("value");

            if (_writeState != WriteState.Attribute)
            {
                throw new InvalidOperationException(
                    string.Format("在错误的状态 [{0}] 下调用了 AddAttribute", _writeState));
            }

            _writeState = WriteState.Attribute;
        }



        /// <summary>
        /// Ends the tag.
        /// </summary>
        /// <param name="tag">The tag.</param>
        public void EndTag(string tag)
        {
            ValidateName(tag, "tag");
            
            string expectedTag = _stack.Pop();
            if (tag != expectedTag)
            {
                throw new InvalidOperationException(
                    string.Format("结束标签 [{0}] 不是期望的标签 [{1}]", tag,expectedTag ));
            }

            if (_writeState != WriteState.Content && // 在内容后关闭标签
                _writeState != WriteState.Attribute && // 立即关闭标签
                _writeState != WriteState.Start
                )
            {
                throw new InvalidOperationException(
                    string.Format("在错误的状态 [{0}] 下调用了 EndTag", _writeState));
            }

            _writeState = WriteState.Start;
        }



        /// <summary>
        /// Appends the content.
        /// </summary>
        /// <param name="str">The STR.</param>
        public void AppendContent(string str)
        {
            if (str == null) throw new ArgumentNullException("str");

            if (_writeState != WriteState.Attribute && // 封闭标签并开始写内容
                _writeState != WriteState.Content // 在上一个内容后继续
                )
            {
                throw new InvalidOperationException(
                    string.Format("在错误的状态 [{0}] 下调用了 AppendContent", _writeState));
            }

            _writeState = WriteState.Content;
        }


        /// <summary>
        /// Verifies this instance.
        /// </summary>
        /// <returns></returns>
        public string Verify()
        {
            if (_stack.Count > 0)
            {
                return string.Format("一些标签未关闭：{0}", string.Join(",", _stack.ToArray()));
            }

            return null;
        }
    }
}
